#!/bin/env python
# -*- coding: utf-8 -*-

#This file is part of pyLot library.
#
# pyLot is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# pyLot is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with pyLot.  If not, see <http://www.gnu.org/licenses/>.

__author__ = u'Pierre Puiseux, Guillaume Baty'
__copyright__ = u"Copyright 2011-2012 (C) andheo, Université de Pau et des Pays de l'Adour"
__credits__ = [u'Pierre Puiseux', u'Guillaume Baty']
__license__ = "GNU Lesser General Public License"

u"""
Interfaces thread/process
"""

__all__ = [u'CommonProcessHandler']

import time

from traits.api import implements

from _pylot.minimal.path import Path
from _pylot.minimal.utils import check_type, check_implements
from _pylot.minimal.commands import ShellCommand, PythonCommand
from _pylot.minimal.commandgroup import CommandGroup

from _pylot.minimal.interfaces.i_processhandler import IProcessHandler
from _pylot.minimal.interfaces.i_logger import ILogger
from _pylot.minimal.writers import NullWriter

class CommonProcessHandler (object):
  u"""
  Class used to run shell commands and python threads.
  
  Threads and commands are stored in a :class:`~_pylot.minimal.CommandGroup` 
  class. 
  This group can next be run by IProcessHandler, see
  :meth:`~_pylot.minimal.IProcessHandler.run` and 
  :meth:`~_pylot.minimal.IProcessHandler.runCmd`
  
  IProcessHandler provides properties to customise outputs and runs 

  You can change display level with 'io.level' attribute :
    - "silent": NOTHING is displayed (useful for doctests)
    - "standard": normal display
    - "verbose": add more informations
    - "debug": display all informations availables

  You can change maximum of simultaneous process with max_process property.
  Value must be an integer. This property has no effect if wait=True.

  You can change where outputs are displayed using "stdout" and "stderr"
  attributes. stdout is a list of keys.

  Current keys are :
    - "save": save outputs in variables
    - "write": send outputs to io writers
  Default: 
  IProcessHandler.stdout_mode = ['write']
  IProcessHandler.stderr_mode = ['write']

  :var stdout: where standard output is displayed
  :var stderr: where standard error is displayed

  Class also provides formatText methods to ensure text display consistency in
  IProcessHandler implementations.
  """
  implements(IProcessHandler)

  def __init__(self, wait=True, max_process=3) :
    u"""
    :param group: affect a group to process, if None, process is independent
    :type group: name

    ..todo:: 
      properties pour verbose, standard_output, ...
    """
    self._wait = wait
    self._max_process = max_process
    self._wait_list = []

    self.logger = None

  def _setlogger (self, logger):
    if logger is None :
      logger = NullWriter('null')
    check_implements(u'logger', logger, ILogger)
    self._logger = logger

  logger = property(fget=lambda self: self._logger,
                    fset=_setlogger)

  def _setwait (self, value):
    check_type(u'wait', value, bool)
    self._wait = value
    self._setWait(self._wait)

  wait = property (
    fget=lambda self: self._wait,
    fset=_setwait)

  def _setmax_process (self, value):
    check_type(u'value', value, int)
    self._max_process = value
    self._setMaxProcess(self._max_process)

  max_process = property (
    fget=lambda self: self._max_process,
    fset=_setmax_process)

  def _setMaxProcess(self, value):
    u"""
    Additional instructions
    """
    pass

  def _setWait(self, value):
    u"""
    Additional instructions
    """
    pass

  def _run(self, cmd_group, wait):
    raise NotImplementedError

  def _runCmd(self, cmd, args, working_directory, wait):
    raise NotImplementedError

  def _requireGroups(self, *args):
    raise NotImplementedError

  def _getRunningProcess(self):
    raise NotImplementedError

  def _getInQueueProcess(self):
    raise NotImplementedError

  def getRunningProcess(self):
    return self._getRunningProcess()

  def getInQueueProcess(self):
    return self._getInQueueProcess()

  def run(self, command_group, wait=None):
    u"""
    :param command_group: command group you want to run
    :type command_group: :class:`~_pylot.minimal.CommandGroup`
    :param wait: wait command_group run finish, [default: use ProcessHandler value]
    :type wait: bool or None
    """
    check_type(u'wait', wait, bool, none=True)
    check_type(u'command_group', command_group, CommandGroup)
    if wait is None :
      wait = self.wait
    command_group = self._run(command_group, wait)
    check_type(u'returned command_group', command_group, CommandGroup)

    return command_group

  def runCmd(self, cmd, args=None, cmd_id=None, alias=None,
             working_directory=None, wait=None):
    u"""
    Create a CommandGroup object with only one ShellCommand and run it
    """
    check_type(u'cmd', cmd, [unicode, ShellCommand, Path])
    cg = CommandGroup(u'command', u'Simple Command')
    if working_directory is not None :
      working_directory = Path(working_directory)
      if working_directory.isdir() :
        cg.working_directory = working_directory
    if isinstance(cmd, ShellCommand) :
      cg.addCmd(cmd)
    else :
      cg.addShellCmd(unicode(cmd), args, cmd_id, alias)
    return self.run(cg, wait)

  def clearQueueProcess(self):
    #TODO: écrire cette méthode / implement this method
    pass

  def killAllProcess(self):
    for process in self.getInQueueProcess() :
      process.kill()
    self.clearQueueProcess()
    for process in self.getRunningProcess() :
      process.kill()

  def requireGroups(self, *args):
    u"""
    Define required groups.
    IProcessHandler is locked until all required groups executions are not 
    successfully finished. If a required fails, IProcessHandler is unlocked
    and an error message is sent.
    
    :param args: one or more command groups
    :type args: CommandGroup
    """
    self._requireGroups(*args)

  def formatTextCmdExecTime (self, command):
    u"""
    :return: text, style, level
    """
    if issubclass(type(command), (ShellCommand, PythonCommand)) :
      t = command.exec_time
      return tr_(u'"%(ID)s" execution time : %(TIME).4fs\n') % dict(
                ID=command.cmd_id, TIME=t), u'info', u'verbose'
    else :
      return u'', u'std', u'standard'

  def formatTextCmdGroupExecTime (self, command_group):
    u"""
    :return: (formated text, style, level)
    """
    t = command_group.exec_time
    return tr_(u'"%(ID)s" execution time : %(TIME).4fs\n') % dict(
               ID=command_group.group_id, TIME=t), u'info', u'standard'

  def formatTextCmdStarted (self, command):
    u"""
    :return: (formated text, style, level)
    """
    if issubclass(type(command), ShellCommand) :
      pwd = unicode(command.working_directory.relpath())
      if len(pwd.split(u'..')) > 2 :
        pwd = command.working_directory

      return (tr_(u'start shell command "%s" :\n - pwd: %s\n - cmd: %s\n') % (
        command.cmd_id, pwd, command.shellUString()),
        u'info', u'verbose')
    elif issubclass(type(command), PythonCommand) :
      return tr_(u'start python command "%s"\n') % (command.cmd_id), u'info', u'verbose'
    else :
      return tr_(u'start command "%s"\n') % (command.cmd_id), u'info', u'debug'

  def formatTextCmdGroupStarted (self, command_group):
    u"""
    :return: (formated text, style, level)
    """
    cg = command_group
    t = time.localtime ()
    text = u'[%02d:%02d:%02d]' % (t.tm_hour, t.tm_min, t.tm_sec)
    return tr_(u'%(TEXT)s Started "%(ALIAS)s" (%(ID)s)') % dict(TEXT=text,
               ALIAS=cg.alias, ID=cg.group_id), u'title', u'standard'

  def formatTextCmdGroupFinished (self, command_group, rcode=0):
    u"""
    :return: (formated text, style, level)
    """
    cg = command_group
    t = time.localtime ()
    text = u'[%02d:%02d:%02d]' % (t.tm_hour, t.tm_min, t.tm_sec)
    if rcode == 0 :
      return tr_(u'%(TEXT)s %(ID)s finished !\n') % dict(TEXT=text,
                 ID=cg.group_id), u'warn', u'standard'
    else :
      return tr_(u'%(TEXT)s %(ID)s fails (return code = %(RCODE)s) !\n') % dict(
                 TEXT=text, ID=cg.group_id, RCODE=cg.return_code), u'warn', u'standard'

